// -----------------------------
// Author: 		Dhaenens Gert	
// Creation: 	2/19/2015 10:39:31 PM				
// -----------------------------

#include "JobManager.h"

#include "Engine.h"
#include "Job.h"

#include "PlatformProvider.h"

#include "IThread.h"
#include "IMutex.h"

#include "IPlatform.h"

namespace Flow
{

	JobManager::JobManager()
		: m_MaxThreads(0)
		, m_Threads(nullptr)
		, m_QueueLock(nullptr)
	{
	}

	JobManager::~JobManager()
	{
		ClearAll(true);

		for (UInt32 i = 0; i < m_MaxThreads; ++i)
			SafeReleaseInterface(&m_Threads[i]);
		delete[] m_Threads;

		SafeReleaseInterface(&m_QueueLock);
	}

	Bool JobManager::Initialize()
	{
		// Limit the amount of max threads on Windows Phone to 2
		if (Engine::Platform->IsPlatform(Platform::WindowsPhone8))
			m_MaxThreads = 2;
		// On Win32 use the amount of processors
		else
			m_MaxThreads = Engine::Platform->GetProcessorCount();

		// Create the Thread Array
		m_Threads = new IThread*[m_MaxThreads];
		for (UInt32 i = 0; i < m_MaxThreads; ++i)
			m_Threads[i] = PlatformProvider::QueryInterface<IThread>("Thread");

		// Create the Queue Lock
		m_QueueLock = PlatformProvider::QueryInterface<IMutex>("Mutex");
		if (!m_QueueLock)
			return false;

		return true;
	}
	void JobManager::ProcessJobs(ProcessOption::Enum option)
	{
		if (m_Jobs.empty())
			return;

		if (option == ProcessOption::ProcessOne)
			ProcessSingleJob();
		else
		{
			while (!m_Jobs.empty())
				ProcessSingleJob();
		}
	}
	void JobManager::ProcessSingleJob()
	{
		IThread* lpThread = GetAvailableThread();
		if (!lpThread)
			return;

		m_QueueLock->Lock();
		Job* lpJob = m_Jobs.front();
		m_Jobs.pop();
		m_QueueLock->Unlock();

		lpThread->Start(JobManager::ThreadProc, lpJob);
	}
	void JobManager::ThreadProc(void* lpArgs)
	{
		Job* lpJob = reinterpret_cast<Job*>(lpArgs);

		lpJob->Run();
		delete lpJob;
	}

	IThread* JobManager::GetAvailableThread()
	{
		for (UInt32 i = 0; i < m_MaxThreads; ++i)
		{
			if (!m_Threads[i]->IsRunning())
				return m_Threads[i];
		}
		return nullptr;
	}

	void JobManager::QueueJob(Job* lpJob)
	{
		m_QueueLock->Lock();
		m_Jobs.push(lpJob);
		m_QueueLock->Unlock();
	}

	void JobManager::ClearJobs()
	{
		m_QueueLock->Lock();
		while (!m_Jobs.empty())
		{
			delete m_Jobs.front();
			m_Jobs.pop();
		}
		m_QueueLock->Unlock();
	}
	void JobManager::ClearThreads(Bool bWaitForThreads)
	{
		for (UInt32 i = 0; i < m_MaxThreads; ++i)
		{
			IThread* lpThread = m_Threads[i];
			if (!lpThread)
				continue;
			if (!lpThread->IsRunning())
				continue;

			if (bWaitForThreads)
				lpThread->WaitForExit();
			else
				lpThread->Stop();
		}
	}
	void JobManager::ClearAll(Bool bWaitForThreads)
	{
		ClearThreads(bWaitForThreads);
		ClearJobs();
	}

	void JobManager::WaitForJobs() const
	{
		Bool areJobsRunning = true;
		while (areJobsRunning)
		{
			areJobsRunning = false;
			for (UInt32 i = 0; i < m_MaxThreads; ++i)
			{
				if (m_Threads[i]->IsRunning())
				{
					areJobsRunning = true;
					break;
				}
			}

			if (!areJobsRunning)
				break;

			Engine::Platform->Sleep(5);
		}
	}
	Bool JobManager::HasJobsAvailable() const
	{
		return (!m_Jobs.empty());
	}

}